﻿using System;
using System.Collections.Generic;
using System.DirectoryServices;
using System.Reflection;
using NEasy.Ldap.Attributes;
using NEasy.Ldap.Entity;

namespace NEasy.Ldap.Extension
{
    /// <summary>
    /// extensions methods for <see cref="LdapAttributeAttribute"/>
    /// </summary>
    internal static class LadpAdAttributeExtensions
    {
        /// <summary>
        /// populates all ldap attributes to the source
        /// </summary>
        internal static void FillWithLdapAttributes<T>(this T source, SearchResult searchResult) where T : LdapObjectBase
        {
            if (source == null)
                throw new NullReferenceException("source cannot be null");

            // validate class for ldap object
            var type = typeof(T);
            var classAttributes = type.GetCustomAttributes(typeof(LdapObjectAttribute), true);
            if (classAttributes.Length != 1)
                throw new CustomAttributeFormatException("class attribute LdapObject expected");

            // iterate over all properties
            foreach (var propertyInfo in type.GetProperties(BindingFlags.Instance | BindingFlags.Public))
            {
                if (!propertyInfo.CanWrite) continue; // only if we can write the property

                var attributes = propertyInfo.GetCustomAttributes(typeof(LdapAttributeAttribute), true);
                if (attributes.Length != 1) continue; // exactly one attribute expected!
                var ldapAttName = ((LdapAttributeAttribute)attributes[0]).LdapAttributeName;

                if (!searchResult.Properties.Contains(ldapAttName)) continue; // ignore non existing attributes
                var searchResultValue = searchResult.Properties[ldapAttName][0];

                propertyInfo.SetValue(source, searchResultValue.ToString(), null);
            }
        }

        /// <summary>
        /// populates all ldap attributes to the source
        /// </summary>
        internal static KeyValuePair<string, object> GetPrimaryKeyValue<T>(this T source) where T : LdapObjectBase
        {
            if (source == null)
                throw new NullReferenceException("source cannot be null");

            // validate class for ldap object
            var type = typeof(T);
            var classAttributes = type.GetCustomAttributes(typeof(LdapObjectAttribute), true);
            if (classAttributes.Length != 1)
                throw new CustomAttributeFormatException("class attribute LdapObject expected");

            // iterate over all properties
            foreach (var propertyInfo in type.GetProperties(BindingFlags.Instance | BindingFlags.Public))
            {
                if (!propertyInfo.CanWrite) continue; // only if we can write the property

                var attributes = propertyInfo.GetCustomAttributes(typeof(LdapAttributeAttribute), true);
                if (attributes.Length != 1) continue; // exactly one attribute expected!
                var ldapAttribute = (LdapAttributeAttribute)attributes[0];

                if (ldapAttribute.IsPrimary)
                    return new KeyValuePair<string, object>(ldapAttribute.LdapAttributeName, propertyInfo.GetValue(source, null));
            }

            throw new CustomAttributeFormatException("this method expects exactly one property defined as primary key");
        }

        /// <summary>
        /// populates all ldap attributes to the source
        /// </summary>
        internal static void SaveValuesTo<T>(this T source, DirectoryEntry entity) where T : LdapObjectBase
        {
            if (source == null)
                throw new NullReferenceException("source cannot be null");

            // validate class for ldap object
            var type = typeof(T);
            var classAttributes = type.GetCustomAttributes(typeof(LdapObjectAttribute), true);
            if (classAttributes.Length != 1)
                throw new CustomAttributeFormatException("class attribute LdapObject expected");

            // iterate over all properties
            foreach (var propertyInfo in type.GetProperties(BindingFlags.Instance | BindingFlags.Public))
            {
                if (!propertyInfo.CanRead) continue; // only if we can read the property

                // get LDAP attribute name
                var attributes = propertyInfo.GetCustomAttributes(typeof(LdapAttributeAttribute), true);
                if (attributes.Length != 1) continue; // exactly one attribute expected!
                var ldapAtt = (LdapAttributeAttribute)attributes[0];
                if (!ldapAtt.Create) continue;

                // get property value and update LDAP value
                var value = propertyInfo.GetValue(source, null);
                entity.Properties[ldapAtt.LdapAttributeName].Clear();
                if (value != null) entity.Properties[ldapAtt.LdapAttributeName].Value = value;//.Add(value);
            }
        }

        internal static void UpdateValuesTo<T>(this T source, DirectoryEntry entity) where T : LdapObjectBase
        {
            if (source == null)
                throw new NullReferenceException("source cannot be null");

            // validate class for ldap object
            var type = typeof(T);
            var classAttributes = type.GetCustomAttributes(typeof(LdapObjectAttribute), true);
            if (classAttributes.Length != 1)
                throw new CustomAttributeFormatException("class attribute LdapObject expected");

            // iterate over all properties
            foreach (var propertyInfo in type.GetProperties(BindingFlags.Instance | BindingFlags.Public))
            {
                if (!propertyInfo.CanRead) continue; // only if we can read the property

                // get LDAP attribute name
                var attributes = propertyInfo.GetCustomAttributes(typeof(LdapAttributeAttribute), true);
                if (attributes.Length != 1) continue; // exactly one attribute expected!
                var ldapAtt = (LdapAttributeAttribute)attributes[0];
                if (!ldapAtt.Update) continue;

                // get property value and update LDAP value
                var value = propertyInfo.GetValue(source, null);
                entity.Properties[ldapAtt.LdapAttributeName].Clear();
                if (value != null) entity.Properties[ldapAtt.LdapAttributeName].Value = value;//.Add(value);
            }
        }
    }
}
